@namespace RazorConsole.Components

@using System
@using System.Collections.Generic
@using System.Globalization
@using System.Threading.Tasks
@using Microsoft.AspNetCore.Components
@using RazorConsole.Components
@using Microsoft.AspNetCore.Components.Web
@using Spectre.Console

<div
     @onfocus="OnFocus"
     @onfocusout="OnFocusOut"
     @onkeydown="OnKeyDown"
     @attributes="AdditionalAttributes">
    <Rows Expand="@Expand">
        @foreach (var option in Options)
        {
            var isSelected = string.Equals(option, Value, StringComparison.Ordinal);
            var isFocused = string.Equals(option, FocusedValue, StringComparison.Ordinal);
            var indicator = isSelected ? SelectedIndicator : UnselectedIndicator;
            <Columns>
                <Markup Content="@($" {indicator} {option}")"
                        Foreground="@(isFocused ? GetFocusAdjustedColor(SelectedOptionForeground) : GetFocusAdjustedColor(OptionForeground))"
                        Decoration="@(isFocused ? GetFocusAdjustedDecoration(SelectedOptionDecoration) : GetFocusAdjustedDecoration(OptionDecoration))" />
            </Columns>
        }
    </Rows>
</div>

@code {
    /// <summary>
    /// Additional attributes to apply to the root element.
    /// </summary>
    [Parameter(CaptureUnmatchedValues = true)]
	public IReadOnlyDictionary<string, object?>? AdditionalAttributes { get; set; }

    // Basic parameters
    /// <summary>
    /// Indicates whether the select currently has focus.
    /// </summary>
    [Parameter]
    public bool IsFocused { get; set; }

    /// <summary>
    /// Event raised when the focus state changes.
    /// </summary>
    [Parameter]
    public EventCallback<bool> IsFocusedChanged { get; set; }

    /// <summary>
    /// Character used to indicate the focused option.
    /// </summary>
    [Parameter]
    public char SelectedIndicator { get; set; } = '>';

    /// <summary>
    /// Character used to indicate non-focused options.
    /// </summary>
    [Parameter]
    public char UnselectedIndicator { get; set; } = ' ';

    /// <summary>
    /// Available options rendered by the select.
    /// </summary>
    [Parameter]
    public string[] Options
    {
        get => _options;
        set => _options = value ?? Array.Empty<string>();
    }

    /// <summary>
    /// Currently committed option value.
    /// </summary>
    [Parameter]
    public string? Value { get; set; }

    /// <summary>
    /// Expands the rows layout to fill available space when true.
    /// </summary>
    [Parameter]
    public bool Expand { get; set; } = false;

    /// <summary>
    /// Event raised when the committed value changes.
    /// </summary>
    [Parameter]
    public EventCallback<string?> ValueChanged { get; set; }

    /// <summary>
    /// Currently highlighted option during keyboard navigation (may differ from committed Value).
    /// </summary>
    [Parameter]
    public string? FocusedValue { get; set; }

    /// <summary>
    /// Event raised when the focused option changes.
    /// </summary>
    [Parameter]
    public EventCallback<string?> FocusedValueChanged { get; set; }

    /// <summary>
    /// Foreground color for non-focused options.
    /// </summary>
    [Parameter]
    public Color OptionForeground { get; set; } = Color.White;

    /// <summary>
    /// Text decoration for non-focused options.
    /// </summary>
    [Parameter]
    public Decoration OptionDecoration { get; set; } = Decoration.None;

    /// <summary>
    /// Foreground color for the focused option.
    /// </summary>
    [Parameter]
    public Color SelectedOptionForeground { get; set; } = Color.Chartreuse1;

    /// <summary>
    /// Text decoration for the focused option.
    /// </summary>
    [Parameter]
    public Decoration SelectedOptionDecoration { get; set; } = Decoration.Bold;

    private string[] _options = Array.Empty<string>();

    // Focus-adjusted styling methods
    private Color GetFocusAdjustedColor(Color color) => IsFocused ? color : Color.Grey50;

    private Decoration GetFocusAdjustedDecoration(Decoration decoration) => 
        IsFocused ? decoration : decoration | Decoration.Dim;

    private async Task NotifyFocusedValueChangedAsync()
    {
        if (FocusedValueChanged.HasDelegate)
        {
            await FocusedValueChanged.InvokeAsync(FocusedValue).ConfigureAwait(false);
        }
    }

    private async Task SelectOptionAsync(string option)
    {
        Value = option;
        FocusedValue = option;

        if (ValueChanged.HasDelegate)
        {
            await ValueChanged.InvokeAsync(option).ConfigureAwait(false);
        }

        if (FocusedValueChanged.HasDelegate)
        {
            await FocusedValueChanged.InvokeAsync(option).ConfigureAwait(false);
        }

        await InvokeAsync(StateHasChanged).ConfigureAwait(false);
    }
    private async Task OnFocus()
    {
        IsFocused = true;
        EnsureFocusedValue();

        if (IsFocusedChanged.HasDelegate)
        {
            await IsFocusedChanged.InvokeAsync(IsFocused).ConfigureAwait(false);
        }

        await InvokeAsync(StateHasChanged).ConfigureAwait(false);
    }

    private async Task OnFocusOut()
    {
        IsFocused = false;

        if (IsFocusedChanged.HasDelegate)
        {
            await IsFocusedChanged.InvokeAsync(IsFocused).ConfigureAwait(false);
        }

        await InvokeAsync(StateHasChanged).ConfigureAwait(false);
    }

    private async Task OnKeyDown(KeyboardEventArgs args)
    {
        if (args is null)
        {
            return;
        }

        var key = args.Key;

        if (string.IsNullOrEmpty(key))
        {
            return;
        }

        if (_options.Length == 0)
        {
            return;
        }

        if (IsCommitKey(key) && FocusedValue is not null)
        {
            await SelectOptionAsync(FocusedValue).ConfigureAwait(false);
            return;
        }

        if (IsCancelKey(key))
        {
            FocusedValue = Value;
            await NotifyFocusedValueChangedAsync().ConfigureAwait(false);
            await InvokeAsync(StateHasChanged).ConfigureAwait(false);
            return;
        }

        var selectionChanged = false;

        if (IsDownKey(key))
        {
            selectionChanged = TryMoveSelection(1);
        }
        else if (IsUpKey(key))
        {
            selectionChanged = TryMoveSelection(-1);
        }
        else if (IsTypeAheadCandidate(key))
        {
            selectionChanged = TryTypeAhead(key[0]);
        }

        if (!selectionChanged)
        {
            return;
        }

        await NotifyFocusedValueChangedAsync().ConfigureAwait(false);
        await InvokeAsync(StateHasChanged).ConfigureAwait(false);
    }

    private bool TryMoveSelection(int offset)
    {
        EnsureFocusedValue();

        if (_options.Length == 0)
        {
            return false;
        }

        var currentIndex = FocusedValue is null
            ? -1
            : Array.IndexOf(_options, FocusedValue);

        if (currentIndex < 0)
        {
            currentIndex = offset > 0 ? -1 : 0;
        }

        var nextIndex = currentIndex + offset;

        if (nextIndex < 0)
        {
            nextIndex = _options.Length - 1;
        }
        else if (nextIndex >= _options.Length)
        {
            nextIndex = 0;
        }

        var nextValue = _options[nextIndex];

        if (string.Equals(nextValue, FocusedValue, StringComparison.Ordinal))
        {
            return false;
        }

        FocusedValue = nextValue;
        return true;
    }

    private bool TryTypeAhead(char key)
    {
        EnsureFocusedValue();

        if (char.IsControl(key))
        {
            return false;
        }

        var startIndex = FocusedValue is null
            ? 0
            : Array.IndexOf(_options, FocusedValue);

        var comparison = StringComparison.CurrentCultureIgnoreCase;
        var prefix = key.ToString();

        for (var i = 0; i < _options.Length; i++)
        {
            var candidateIndex = (startIndex + i + 1) % _options.Length;
            var candidate = _options[candidateIndex];

            if (!string.IsNullOrEmpty(candidate) && candidate.StartsWith(prefix, comparison))
            {
                if (!string.Equals(candidate, FocusedValue, StringComparison.Ordinal))
                {
                    FocusedValue = candidate;
                    return true;
                }

                break;
            }
        }

        return false;
    }



    private static bool IsTypeAheadCandidate(string value)
        => value.Length == 1 && !char.IsControl(value[0]);

    private static bool IsDownKey(string key)
        => string.Equals(key, "DownArrow", StringComparison.OrdinalIgnoreCase)
            || string.Equals(key, "ArrowDown", StringComparison.OrdinalIgnoreCase);

    private static bool IsUpKey(string key)
        => string.Equals(key, "UpArrow", StringComparison.OrdinalIgnoreCase)
            || string.Equals(key, "ArrowUp", StringComparison.OrdinalIgnoreCase);



    private static bool IsCommitKey(string key)
        => string.Equals(key, "Enter", StringComparison.OrdinalIgnoreCase)
            || string.Equals(key, "Return", StringComparison.OrdinalIgnoreCase);

    private static bool IsCancelKey(string key)
        => string.Equals(key, "Escape", StringComparison.OrdinalIgnoreCase)
            || string.Equals(key, "Esc", StringComparison.OrdinalIgnoreCase);

    private void EnsureFocusedValue()
    {
        if (_options.Length == 0)
        {
            FocusedValue = null;
            return;
        }

        if (!string.IsNullOrEmpty(FocusedValue))
        {
            var index = Array.IndexOf(_options, FocusedValue);
            if (index >= 0)
            {
                return;
            }
        }

        if (!string.IsNullOrEmpty(Value))
        {
            var selectedIndex = Array.IndexOf(_options, Value);
            if (selectedIndex >= 0)
            {
                FocusedValue = Value;
                return;
            }
        }

        FocusedValue = _options[0];
    }
}
